#pragma once

#include <string>
#include <vector>
#include "IExport.h"
#include "nef_exceptions.h"
#include "nef_import_detect.h"
#include "nef_export_detect.h"
#include "IPartImportsSatisfied.h"
#include "auto_register.h"
#include "type_factory.h"
#include <unordered_map>
#include <set>
#include <typeindex>
#include <utility>
#include <filesystem>
#include "part_composer.h"

namespace fs = std::tr2::sys;

typedef void (*nef_entry_point_function)(std::vector<nef::IExport::Ptr> &);

namespace nef {

class NEF_API Catalog
	: public std::enable_shared_from_this<Catalog>
{
public:
	typedef std::shared_ptr<Catalog> Ptr;

	static Ptr Create();

	/* 
	 * This function takes an object, searches for any required imports, attempts to satisfy them.
	 * If T inherits from the interface IPartImportsSatisfied, then OnImportsSatisfied will be invoked as soon as all imports have been met, but before exports
	 * Finally, once the type has all of its imports, and has been notified of such, this function will scan the type for any exports, and add them to the container
	 */
	template<typename T>
	void ComposePart(T &val)
	{
		// Use the part composer since it will do necessary specializations to better deduce the type
		// being composed. The best example of this is pointer detection
		part_composer<T>::compose(*this, val);
	}

	/*
	 * Searches the container for the specified export of type T, and with the specified contract.
	 * 
	 * required - If true, a failure to find a matching export will result in an export_exception being thrown,
	 *            otherwise, a null pointer is returned
	 */
	template<typename T>
	std::shared_ptr<T> GetExportedValue(const std::string &contractName = NO_CONTRACT, bool required = true)
	{
		IExport::Ptr exp = FindExport<T>(contractName);

		if (!exp)
		{
			if (required)
				throw export_exception(GetFailExportString<T>(contractName));
			else
				return std::shared_ptr<T>();
		}

		try
		{
			return exp->GetValue<T>(*this);
		}
		catch (export_exception &ex)
		{
			RemoveExport(exp);

			if (required)
				throw export_exception(GetFailExportString<T>(contractName) + " because of the following...", ex) ;

			// The type wasn't required, so just return null
			return std::shared_ptr<T>();
		}
	}

	/*
	 * Searches the container for all of the matching exports [T, Contract] and adds them to 'ct'
	 * 
	 * ct - Any container that supports 'push_back(std::shared_ptr<T>)'
	 */
	template<typename T, typename Container>
	void GetExportedValues(Container &ct, const std::string &contractName = NO_CONTRACT)
	{
		std::vector<IExport::Ptr> xps;
		FindAllExports<T>(contractName, xps);

		for (const auto &xp : xps)
		{
			try
			{
				auto val = xp->GetValue<T>(*this);

				ct.push_back(val);
			}
			catch (export_exception &)
			{
				RemoveExport(xp);
			}
		}
	}

	/*
	 * Returns a type_factory<T> which is a special export wrapper that allows consumers to request new instances ad-hoc
	 */
	template<typename T>
	typename type_factory<T>::Ptr GetExportFactory(const std::string &contractName = NO_CONTRACT, bool required = true)
	{
		IExport::Ptr exp = FindExport<T>(contractName);

		if (!exp)
		{
			if (required)
				throw std::exception("Unable to resolve the export.");
			else
				return type_factory<T>::Ptr();
		}

		return type_factory<T>::Ptr(new export_type_factory<T>(shared_from_this(), exp));
	}

	/*
	 * Adds the specified part to the catalog
	 * 
	 * scope - Object that defines the instance policy for the actual part. see nef_scope.h for standard implementations
	 */
	template<typename Scope>
	void AddPart(const Scope &scope, const std::string &contractName = NO_CONTRACT)
	{
		if (CheckStored<Scope>(contractName))
			return;

		/*static bool ALREADY_STORED = false;

		if (ALREADY_STORED)
			return;*/

		auto ex = CreateExport(scope, contractName);

		_exports.push_back(ex);

		//ALREADY_STORED = true;
	}

	/*
	 * Resolves all declared imports for the specified type. This function does not work polymorphically, i.e. calling this on a base class will result in derived imports potentially being ignored.
	 * 
	 * Future versions may address this problem.
	 */
	template<typename T>
	void SatisfyImports(T &val)
	{
		resolve_imports(*this, val);
	}

	/*
	 * Finds the actual export object that matches [T, Contract]
	 */
	template<typename T>
	IExport::Ptr FindExport(const std::string &contractName = NO_CONTRACT)
	{
		// Make sure that we have pulled in all of the dynamic exports
		Init();

		// Check to see if T has an inline export that would help resolve this
		auto_register<T>(*this);

		auto iter = _exports.begin();
		auto end = _exports.end();
		bool retry = false;

		// This could be grossly inefficient, todo: optimize part retrieval
		for (; iter != end; ++iter)
		{
			// Retry will get set to true if the export for this type contains the export that we actually need
			if ((*iter)->IsMatch<T>(contractName, *this, retry))
				return *iter;

			if (retry)
			{
				iter = _exports.begin();
				end = _exports.end();
				retry = false;
			}
		}

		// If this isn't the global catalog, then try to resolve the export there
		if (this != s_statalog.get())
			return s_statalog->FindExport<T>(contractName);

		return IExport::Ptr();
	}

	/*
	 * Finds all of the actual export objects that match [T, Contract].
	 * 
	 * ct - Any class that supports push_back(IExport::Ptr)
	 */
	template<typename T, typename Container>
	void FindAllExports(const std::string &contractName, Container &ct)
	{
		// TODO: This construct is very similar to FindExport, see if there is any way to merge the code path

		// Make sure that we have pulled in all of the dynamic exports
		Init();

		auto iter = _exports.begin();
		auto end = _exports.end();
		bool retry = false;

		for (; iter != end; ++iter)
		{
			// Retry will get set to true if the export for this type contains the export that we actually need
			if ((*iter)->IsMatch<T>(contractName, *this, retry))
				ct.push_back(*iter);

			if (retry)
			{
				ct.clear();
				iter = _exports.begin();
				end = _exports.end();
				retry = false;
			}
		}

		if (this != s_statalog.get())
			s_statalog->FindAllExports<T>(contractName, ct);
	}

	/*
	 * Adds any inline exports declared by ExportType to the container
	 */
	template<typename ExportType>
	void Register()
	{
		resolve_export<ExportType>(*this);
	}

	/*
	 * Loads all assemblies in the specified folder (and sub-folders), and adds any exports to the container.
	 * 
	 * Any assemblies that fail to load are ignored
	 */
	void LoadFolder(const fs::path &p);

	/*
	 * Loads the specified assembly and adds any exports to the container.
	 * 
	 * If the load fails for any reason, false is returned
	 */
	bool LoadAssembly(const fs::path &p);

	/*
	 * NEF always creates a global catalog that catches dynamic exports
	 */
	static Catalog::Ptr GlobalCatalog();

	/*
	 * INTERNAL: Used by the dynamic registration system
	 */
	void AddEntryPoint(nef_entry_point_function ex);

	/*
	 * Stores all exports associated with the type
	 */
	template<typename T>
	void StoreExports(T &val)
	{
		static bool ALREADY_STORED = false;

		if (ALREADY_STORED)
			return;

		resolve_exports(*this, val);

		ALREADY_STORED = true;
	}

private:
	// Prevent construction to enforce that all catalogs are actually shared pointers
	Catalog();

	void Init();
	void LoadComponentsInternal(const fs::path &p);
	bool LoadAssemblyInternal(const fs::path &p);

	template<typename Scope>
	bool CheckStored(const std::string &contractName)
	{
		//auto typeIdx = std::type_index(typeid(Scope));

		//auto iter = _alreadyExported.find(typeIdx);

		//if (iter != _alreadyExported.end())
		//{
		//	auto iter2 = iter->second.find(contractName);

		//	// This export with the same contract has already been added to the container
		//	if (iter2 == iter->second.end())
		//		iter->second.insert(contractName);
		//	else
		//		return true;
		//}
		//else
		//{
		//	auto iter2 = _alreadyExported.insert(std::make_pair(typeIdx, std::set<std::string>()));

		//	iter2.first->second.insert(contractName);
		//}

		return false;
	}

	void RemoveExport(const IExport::Ptr &xp)
	{
		auto xpIter = std::find(_exports.begin(), _exports.end(), xp);

		if (xpIter != _exports.end())
			_exports.erase(xpIter);
	}

	template<typename T>
	std::string GetFailExportString(const std::string &contractName)
	{
		return std::string("Unable to resolve export '") + typeid(T).name() + " [\"" + contractName + "\"]'";
	}

	std::vector<IExport::Ptr> _exports;
	std::unordered_map<std::type_index, std::set<std::string>> _alreadyExported;
	std::vector<nef_entry_point_function> _entryPoints;

	static Catalog::Ptr s_statalog;
};

}